package com.weilx959.spt.app.context;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.weilx959.spt.app.controller.GeneralConfigFormController;
import com.weilx959.spt.app.model.AppConfig;
import com.weilx959.spt.app.utils.ResourceBundleUtil;
import com.weilx959.spt.core.LanguageContext;
import com.weilx959.spt.core.config.Config;
import com.weilx959.spt.core.constants.CommonConstants;
import com.weilx959.spt.core.utils.FXMLUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;

/**
 * 配置上线文参数
 *
 * @author weilx
 * @since 2024年6月2日
 */
@Slf4j
public class ConfigContext {
    private ConfigContext() {}

    private static final ThreadLocal<AppConfig> CONFIG_THREAD_LOCAL = new ThreadLocal<>();

    private static final ThreadLocal<Map<String, Object>> CONFIG_MAP_THREAD_LOCAL = new ThreadLocal<>();

    private static final ThreadLocal<Map<String, Config>> CONFIG_CACHE_THREAD_LOCAL = new ThreadLocal<>();

    public static AppConfig get() {
        return CONFIG_THREAD_LOCAL.get();
    }

    public static void set(AppConfig config) {
        CONFIG_THREAD_LOCAL.set(config);
    }

    public static void destroy() {
        CONFIG_THREAD_LOCAL.remove();
    }

    public static void load() throws IOException {
        Path filePath = Paths.get(CommonConstants.DEFAULT_APP_PATH, CommonConstants.APP_CONFIG_FILE);

        if (!filePath.toFile().exists()) {
            ConfigContext.set(new AppConfig());
            LanguageContext.set(CommonConstants.DEFAULT_LANGUAGE);
            return;
        }
        String appConfigString = Files.readString(filePath, StandardCharsets.UTF_8);
        log.info("app config: {}", appConfigString);
        if (StringUtils.isNotBlank(appConfigString)) {
            ConfigContext.set(JSON.parseObject(appConfigString, AppConfig.class));
            LanguageContext.set(Optional.ofNullable(ConfigContext.get())
                .map(AppConfig::getGeneral)
                .map(AppConfig.General::getLanguage)
                .orElse(CommonConstants.DEFAULT_LANGUAGE));
        } else {
            ConfigContext.set(new AppConfig());
            LanguageContext.set(CommonConstants.DEFAULT_LANGUAGE);
        }
    }

    public static void save() throws IOException {
        // 保存语言
        AppConfig appConfig = Optional.ofNullable(ConfigContext.get()).orElseGet(AppConfig::new);
        AppConfig.General general = Optional.ofNullable(appConfig.getGeneral()).orElseGet(AppConfig.General::new);
        general.setLanguage(LanguageContext.get());
        appConfig.setGeneral(general);
        // 保存文件
        Files.writeString(Paths.get(CommonConstants.DEFAULT_APP_PATH, CommonConstants.APP_CONFIG_FILE),
            JSON.toJSONString(appConfig, JSONWriter.Feature.PrettyFormat),
            StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
        destroy();
        destroyConfigMap();
    }

    public static Map<String, Object> reloadConfigMap() {
        loadConfigMap();
        return CONFIG_MAP_THREAD_LOCAL.get();
    }

    public static Map<String, Object> getConfigMap() {
        Map<String, Object> configMap = CONFIG_MAP_THREAD_LOCAL.get();

        if (Objects.isNull(configMap)) {
            loadConfigMap();
            configMap = CONFIG_MAP_THREAD_LOCAL.get();
        }
        return configMap;
    }

    public static void loadConfigMap() {
        log.info("Start to load config controls...");
        ResourceBundle bundle = ResourceBundleUtil.get();
        GeneralConfigFormController generalConfigFormController = FXMLUtil.getController("com/weilx959/spt/app/config-general-view.fxml", bundle);

        Map<String, Object> configMap = new HashMap<>();
        configMap.put("General", generalConfigFormController.getConfig());
        CONFIG_MAP_THREAD_LOCAL.set(configMap);
        CONFIG_CACHE_THREAD_LOCAL.set(new HashMap<>());
    }

    public static void destroyConfigMap() {
        CONFIG_MAP_THREAD_LOCAL.remove();
        CONFIG_CACHE_THREAD_LOCAL.remove();
    }

    /**
     * 获取配置样式
     *
     * @param key 钥匙
     * @return {@link Config }
     */
    public static Config getConfig(String key) {
        if (CONFIG_CACHE_THREAD_LOCAL.get().containsKey(key)) {
            log.info("Get config, match cache key : {}", key);
            return CONFIG_CACHE_THREAD_LOCAL.get().get(key);
        }
        Config config = ConfigContext.getUncachedConfig(key);
        CONFIG_CACHE_THREAD_LOCAL.get().put(key, config);
        return config;
    }

    /**
     * 获取未缓存配置
     *
     * @param key 钥匙
     * @return {@link Config }
     */
    public static Config getUncachedConfig(String key) {
        Map<?, ?> configMap = ConfigContext.getConfigMap();

        if (MapUtils.isEmpty(configMap) || StringUtils.isBlank(key)) {
            log.info("ConfigMap is empty or key is blank!");
            return null;
        }
        String[] keys = StringUtils.split(key, CommonConstants.Symbol.DOT);

        for (int i = 0; i < keys.length; i++) {
            if (MapUtils.isEmpty(configMap)) {
                log.info("ConfigMap is empty!");
                return null;
            }
            Object value = configMap.get(keys[i]);

            if (i == keys.length - 1) { // 最后的key，取配置
                if (value instanceof Config config) {
                    return config;
                }
            } else if (value instanceof Map<?, ?> map) {
                configMap = map;
            } else {
                log.error("Unsupported value type: {}", value);
                return null;
            }
        }
        log.info("No match config!");
        return null;
    }

    /**
     * 设置配置数据
     *
     * @param configData 配置数据
     */
    public static void setConfigData(Map<String, Object> configData) {
        if (Objects.isNull(configData)) {
            configData = new HashMap<>();
        }
        AppConfig newConfig = JSON.parseObject(JSON.toJSONString(configData), AppConfig.class);
        ConfigContext.set(newConfig);
    }
}
